Zum Hauptinhalt springen

Einführung in Qiskit

In diesem Notebook erkunden wir, wie wir Quantengates und Quantenschaltkreise mit Qiskit programmieren und sogar auf Simulatoren und echten Quantencomputern mit Qiskit-Patterns ausführen können. Anschließend stellen wir verschiedene Möglichkeiten zur Informationskodierung vor und schließen mit einem Bonusbeispiel zur Quantenteleportation ab.

Bevor du beginnst

Folge den Anweisungen unter Installieren und einrichten, falls du das noch nicht getan hast, einschließlich der Schritte zur Einrichtung für die Nutzung der IBM Quantum™ Platform.

Es wird empfohlen, die Jupyter-Entwicklungsumgebung für die Interaktion mit Quantencomputern zu verwenden. Stelle sicher, dass du die empfohlene zusätzliche Visualisierungsunterstützung installierst ('qiskit[visualization]'). Du benötigst außerdem das Paket matplotlib für den zweiten Teil dieses Beispiels.

Um mehr über Quantencomputing im Allgemeinen zu erfahren, besuche den Kurs zu den Grundlagen der Quanteninformation in IBM Quantum Learning.

Importe

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime
# Import necessary modules for this notebook
import time
import qiskit

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector, plot_state_qsphere
from qiskit_aer import AerSimulator
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.visualization import plot_histogram
print(qiskit.__version__)
2.3.1

Um deine Quantenschaltkreise auf Hardware auszuführen, musst du zunächst dein Konto einrichten. Das geht folgendermaßen:

  1. Gehe zur aktualisierten IBM Quantum® Platform.
  2. Gehe in die obere rechte Ecke (wie im Bild oben gezeigt), erstelle deinen API-Token und kopiere ihn an einen sicheren Ort.
  3. Ersetze in der nächsten Zelle deleteThisAndPasteYourAPIKeyHere durch deinen API-Schlüssel.
  4. Gehe in die untere linke Ecke (wie im Bild oben gezeigt) und erstelle deine Instanz. Wähle dabei unbedingt den Open Plan.
  5. Nachdem die Instanz erstellt wurde, kopiere den zugehörigen CRN-Code. Möglicherweise musst du die Seite aktualisieren, um die Instanz zu sehen.
  6. Ersetze in der Zelle unten deleteThisAndPasteYourCRNHere durch deinen CRN-Code.

Weitere Informationen zur Einrichtung deines IBM Cloud®-Kontos findest du in diesem Leitfaden.

⚠️ Hinweis: Behandle deinen API-Schlüssel wie ein sicheres Passwort. Weitere Informationen zur Verwendung deines API-Schlüssels in sicheren und nicht vertrauenswürdigen Umgebungen findest du im Cloud-Einrichtungsleitfaden.

#your_api_key = "deleteThisAndPasteYourAPIKeyHere"
#your_crn = "deleteThisAndPasteYourCRNHere"

QiskitRuntimeService.save_account(
channel="ibm_quantum_platform",
token=your_api_key,
instance=your_crn,
overwrite=True
)

1. Quantum Gates und Quantum Circuits

Quantum Circuits sind Modelle für Quantenberechnungen, bei denen eine Berechnung aus einer Folge von Quantum Gates besteht. Schauen wir uns einige der gängigen Quantum Gates an.

X Gate

Ein X Gate entspricht einer Rotation um die X-Achse der Bloch-Sphäre um π\pi Bogenmass. Es bildet 0|0\rangle auf 1|1\rangle und 1|1\rangle auf 0|0\rangle ab. Es ist das Quantenäquivalent des NOT-Gates für klassische Computer und wird manchmal als Bit-Flip bezeichnet.

X=(0110)X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix}

# Let's apply an X-gate on a |0> qubit
qc = QuantumCircuit(1)
qc.x(0)
qc.draw(output='mpl')

Quantum circuit diagram

# Let's see Bloch sphere visualization
sv = Statevector(qc)
plot_bloch_multivector(sv)

Code output

H Gate

Ein Hadamard Gate stellt eine Rotation um π\pi um die Achse dar, die zwischen der XX-Achse und der ZZ-Achse liegt. Es bildet den Basiszustand 0|0\rangle auf 0+12\frac{|0\rangle + |1\rangle}{\sqrt{2}} ab, was bedeutet, dass eine Messung mit gleicher Wahrscheinlichkeit 1 oder 0 ergibt und eine 'Superposition' der Zustände erzeugt. Dieser Zustand wird auch als +|+\rangle geschrieben.

H=12(1111)H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \\ \end{pmatrix}

# Let's apply an H-gate on a |0> qubit
qc = QuantumCircuit(1)
qc.x(0)
qc.h(0)
qc.draw(output='mpl')

Quantum circuit diagram

# Let's see Bloch sphere visualization
sv = Statevector(qc)
plot_bloch_multivector(sv)

Code output

CX Gate (CNOT Gate)

Das kontrollierte NOT-Gate (auch CNOT oder CX Gate) wirkt auf zwei Qubits. Es führt die NOT-Operation (entspricht der Anwendung eines X Gates) auf dem zweiten Qubit nur dann aus, wenn das erste Qubit 1|1\rangle ist, andernfalls lässt es es unverändert. Hinweis: Qiskit nummeriert die Bits in einem String von rechts nach links.

CX=(1000010000010010)CX = \begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0\\ \end{pmatrix}

# Let's apply a CX-gate on |11>
qc = QuantumCircuit(2)
qc.x(0)
qc.x(1)
qc.cx(0,1)
qc.draw(output='mpl')

Quantum circuit diagram

sv=Statevector(qc)
plot_state_qsphere(sv)

Code output

Erzeuge den ersten Bell-Zustand

ϕ+=12(00+11)|\phi^+ \rangle = \frac{1}{\sqrt 2}(|00 \rangle + |11 \rangle)

# Create a Bell state circuit

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0,1)

# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

# Plot the state using q-sphere visualization
sv = Statevector(qc)
plot_state_qsphere(sv)
# q-sphere is useful for visualizing states when Bloch sphere fails to

Code output

Erzeuge den zweiten Bell-Zustand

ϕ=12(0011)|\phi^- \rangle = \frac{1}{\sqrt 2}(|00 \rangle - |11 \rangle)

# Create a circuit with the second Bell state

qc = QuantumCircuit(2)
qc.x(0)
qc.h(0)
qc.cx(0,1)

qc.draw("mpl")

Quantum circuit diagram

Die Erklärung dafür ist:

H1=12(01)=H|1\rangle=\frac{1}{\sqrt{2} }(|0\rangle-|1\rangle) = |-\rangle
# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

Erzeuge den 3-Qubit-GHZ-Zustand

GHZ=12(000+111)|GHZ \rangle = \frac{1}{\sqrt 2}(|000 \rangle + |111 \rangle)

# Create a circuit with 3-qubit GHZ state

qc= QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)

qc.draw("mpl")

Quantum circuit diagram

# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

Erzeuge den Qiskit-Logo-Zustand

Qiskit=12(0010+1101)|Qiskit \rangle = \frac{1}{\sqrt 2}(|0010 \rangle + |1101 \rangle)

Centered Image
# Create a circuit with the Qiskit logo state

qc = QuantumCircuit(4)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
qc.cx(0,3)
qc.x(1)

# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

2. Ein einfaches Quantenprogramm erstellen und ausführen

Die vier Schritte zum Schreiben eines Quantenprogramms mit Qiskit-Patterns sind:

  1. Das Problem in ein quantennatives Format überführen.

  2. Die Circuits und Operatoren optimieren.

  3. Mit einer Quantenprimitivfunktion ausführen.

  4. Die Ergebnisse analysieren.

2.1 Map the problem to a quantum-native format

In einem Quantenprogramm sind Quantenschaltkreise das native Format zur Darstellung von Quantenanweisungen, und Operatoren repräsentieren die zu messenden Observablen. Wenn du einen Circuit erstellst, legst du normalerweise ein neues QuantumCircuit-Objekt an und fügst ihm dann der Reihe nach Anweisungen hinzu.

Die folgende Code-Zelle erstellt einen Circuit, der den GHZ-Zustand erzeugt – einen Zustand, in dem drei Qubits vollständig miteinander verschränkt sind.

Das Qiskit SDK verwendet die LSb-0-Bitnummerierung, bei der die nthn^{th} Stelle den Wert 1n1 \ll n oder 2n2^n hat. Weitere Details findest du im Thema Bit-ordering in the Qiskit SDK.

# Create a GHZ state circuit

qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

Alle verfügbaren Operationen findest du in der Dokumentation unter QuantumCircuit.

Beim Erstellen von Quantenschaltkreisen musst du auch überlegen, welche Art von Daten nach der Ausführung zurückgegeben werden sollen. Qiskit bietet zwei Möglichkeiten zur Rückgabe von Daten: Du kannst eine Wahrscheinlichkeitsverteilung für eine Menge von Qubits erhalten, die du zu messen wählst, oder du kannst den Erwartungswert einer Observablen erhalten. Bereite deine Arbeitslast so vor, dass dein Circuit auf eine dieser beiden Arten mit Qiskit-Primitiven gemessen wird (im Detail in Schritt 3 erläutert).

Dieses Beispiel misst Erwartungswerte mithilfe des Untermoduls qiskit.quantum_info, das durch Operatoren (mathematische Objekte, die eine Aktion oder einen Prozess darstellen, der einen Quantenzustand verändert) angegeben wird. Die folgende Code-Zelle erstellt sechs Drei-Qubit-Pauli-Operatoren: ZZZ, ZZX, ZII, XXI, ZZI und III.

# Set up six different observables.

observables_labels = ["ZZZ", "ZZX", "ZII", "XXI", "ZZI", "III"]

observables = [SparsePauliOp(label) for label in observables_labels]
print(observables)
[SparsePauliOp(['ZZZ'],
coeffs=[1.+0.j]), SparsePauliOp(['ZZX'],
coeffs=[1.+0.j]), SparsePauliOp(['ZII'],
coeffs=[1.+0.j]), SparsePauliOp(['XXI'],
coeffs=[1.+0.j]), SparsePauliOp(['ZZI'],
coeffs=[1.+0.j]), SparsePauliOp(['III'],
coeffs=[1.+0.j])]

Hier ist zum Beispiel der Operator ZZI eine Kurzschreibweise für das Tensorprodukt ZZIZ\otimes Z\otimes I, was bedeutet, dass Z auf Qubit 2 und Z auf Qubit 1 gemeinsam gemessen wird und Informationen über die Korrelation zwischen Qubit 2 und Qubit 1 gewonnen werden. Erwartungswerte wie dieser werden üblicherweise auch als Z2Z1\langle Z_2 Z_1 \rangle geschrieben.

Wenn der beobachtete Zustand der Drei-Qubit-GHZ-Zustand ist, sollte die Messung von Z2Z1\langle Z_2 Z_1 \rangle gleich 1 sein.

2.2 Optimize the circuits and operators

Bei der Ausführung von Circuits auf einem Gerät ist es wichtig, die im Circuit enthaltenen Anweisungen zu optimieren und die Gesamttiefe (grob die Anzahl der Anweisungen) des Circuits zu minimieren. Dies stellt sicher, dass du die bestmöglichen Ergebnisse erzielst, indem die Auswirkungen von Fehlern und Rauschen reduziert werden. Außerdem müssen die Anweisungen des Circuits der Instruction Set Architecture (ISA) eines Backend-Geräts entsprechen und die Basis-Gates sowie die Qubit-Konnektivität des Geräts berücksichtigen.

Der folgende Code instanziiert ein echtes Gerät, an das ein Job übermittelt werden soll, und transformiert den Circuit und die Observablen so, dass sie der ISA dieses Backends entsprechen. Wenn du deine Zugangsdaten noch nicht gespeichert hast, folge den Anweisungen hier, um dich mit deinem API-Token zu authentifizieren.

# Choose a real backend
service = QiskitRuntimeService(channel='ibm_quantum_platform',)
backend = service.least_busy(min_num_qubits=156)
# print backend details
print(
f"Name: {backend.name}\n"
f"Version: {backend.backend_version}\n"
f"No. of qubits: {backend.num_qubits}\n"
f"Processor type: {backend.processor_type}\n"
)
Name: ibm_marrakesh
Version: 1.0.21
No. of qubits: 156
Processor type: {'family': 'Heron', 'revision': '2'}
# option to use the AerSimulator instead of a real quantum device
seed_sim=42
backend=AerSimulator.from_backend(backend,seed_simulator=seed_sim)

Transpiliere den Circuit in einen ISA-Circuit

# Convert to an ISA circuit and layout-mapped observables.

pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(qc)

isa_circuit.draw("mpl", idle_wires=False)

Quantum circuit diagram

mapped_observables = [
observable.apply_layout(isa_circuit.layout) for observable in observables
]
print(mapped_observables)
[SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIXIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIXIIIIIIIIIIIIIIIIIIXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])]

2.3 Execute using the quantum primitives

Quantencomputer können zufällige Ergebnisse liefern, daher sammelst du normalerweise eine Stichprobe der Ausgaben, indem du den Circuit viele Male ausführst. Du kannst den Wert der Observablen mithilfe der Klasse Estimator schätzen. Estimator ist eine von zwei Primitiven; die andere ist Sampler, mit der Daten von einem Quantencomputer abgerufen werden können. Diese Objekte besitzen eine run()-Methode, die die Auswahl an Circuits, Observablen und Parametern (falls zutreffend) mithilfe eines primitive unified bloc (PUB) ausführt. Wenn du diesen Code auf echter Quantenhardware ausführst, solltest du Fehlerminderungs- und Fehlerunterdrückungstechniken anwenden, um das intrinsische Rauschen des Quantencomputers zu reduzieren.

# Construct the Estimator instance.
estimator = Estimator(mode=backend)
estimator.options.resilience_level = 1
estimator.options.default_shots = 5000

Übermittle einen Job mithilfe der Estimator-Primitive.

# One pub, with one circuit to run against six different observables.
job = estimator.run([(isa_circuit, mapped_observables)])

# Use the job ID to retrieve your job data later
print(f">>> Job ID: {job.job_id()}")
>>> Job ID: 97ecd036-1767-49b0-a1dc-c71638c3c3c4
/Users/jma/miniconda3/envs/3122/lib/python3.12/site-packages/qiskit_ibm_runtime/fake_provider/local_service.py:187: UserWarning: The resilience_level option has no effect in local testing mode.
warnings.warn("The resilience_level option has no effect in local testing mode.")

Nachdem ein Job übermittelt wurde, kannst du entweder warten, bis der Job in deiner aktuellen Python-Instanz abgeschlossen ist, oder die job_id verwenden, um die Daten zu einem späteren Zeitpunkt abzurufen. (Weitere Informationen findest du im Abschnitt zum Abrufen von Jobs.)

Nachdem der Job abgeschlossen ist, kannst du seine Ausgabe über das Attribut result() des Jobs untersuchen.

# This is the result of the entire submission.  You submitted one Pub,
# so this contains one inner result (and some metadata of its own).
job_result = job.result()

# This is the result from our single pub, which had six observables,
# so contains information on all six.
pub_result = job.result()[0]

Jetzt können wir den Circuit auch mithilfe der Sampler-Primitive ausführen

# We include the measurements in the circuit
qc.measure_all()
sampler = Sampler(mode=backend)
qc.draw(output="mpl")

Quantum circuit diagram

Übermittle einen Job mithilfe der Sampler-Primitive.

job_sampler = sampler.run(pm.run([qc]))

# Use the job ID to retrieve your job data later
print(f">>> Job ID: {job_sampler.job_id()}")
# Get the results
results_sampler = job_sampler.result()
>>> Job ID: a6ee4d2f-c80d-4a86-9a76-e4b1a74502e7

2.4 Analyze the results

Der Analyse-Schritt ist typischerweise der Punkt, an dem du deine Ergebnisse nachbearbeitest – zum Beispiel mit Messfehlermitigation oder Zero Noise Extrapolation (ZNE). Du kannst diese Ergebnisse in einen anderen Workflow zur weiteren Analyse einspeisen oder eine Darstellung der wichtigsten Werte und Daten erstellen. Im Allgemeinen ist dieser Schritt spezifisch für dein Problem. Plotte für dieses Beispiel jeden der Erwartungswerte, die für unseren Circuit gemessen wurden.

Die Erwartungswerte und Standardabweichungen für die Observablen, die du dem Estimator übergeben hast, werden über die Attribute PubResult.data.evs und PubResult.data.stds des Job-Ergebnisses abgerufen. Um die Ergebnisse vom Sampler zu erhalten, verwende die Funktion PubResult.data.meas.get_counts(), die ein dict von Messungen in Form von Bitstrings als Schlüssel und den entsprechenden Zählwerten zurückgibt. Weitere Informationen findest du unter Get started with Sampler.

# Plot the result
from matplotlib import pyplot as plt
values = pub_result.data.evs
errors = pub_result.data.stds
# plotting graph
# Plotting with error bars
plt.errorbar(observables_labels, values, yerr=errors, fmt='-o', capsize=5)
plt.xlabel("Observables")
plt.ylabel("Values")
plt.title("Plot of Observables vs Values with Error Bars")
plt.grid(True)
plt.tight_layout()
plt.show()

Plot output

Wir sehen, dass die Observablen ZZIZZI und IIIIII einen Erwartungswert von 1 haben, da ZZIZZI zwei Minuszeichen einführt, die sich gegenseitig aufheben, und IIIIII als Identität wirkt und den GHZ-Zustand unverändert lässt. Die übrigen Observablen haben einen Erwartungswert von 0, da ihre ZZ-Operatoren eine ungerade Anzahl von Minuszeichen einführen oder die XX-Operatoren eine Anzahl von Qubits umklappen, die die überlappenden Zustände orthogonal macht.

Jetzt plotten wir die Ergebnisse für den Sampler

counts_list = results_sampler[0].data.meas.get_counts()
print(counts_list)
print(f"Outcomes : {counts_list}")
display(plot_histogram(counts_list, title="GHZ state"))
{'111': 480, '000': 503, '101': 8, '100': 9, '001': 3, '011': 6, '010': 10, '110': 5}
Outcomes : {'111': 480, '000': 503, '101': 8, '100': 9, '001': 3, '011': 6, '010': 10, '110': 5}

Code output

2.5 Scale to large numbers of qubits

In der Quantencomputing ist Arbeit im Utility-Maßstab entscheidend für den Fortschritt auf diesem Gebiet. Solche Arbeit erfordert Berechnungen in einem viel größeren Maßstab; dabei werden Circuits verwendet, die möglicherweise mehr als 100 Qubits und über 1000 Gates nutzen. Dieses Beispiel macht einen kleinen Schritt in diese Richtung, indem es das GHZ-Problem auf n=10n=10 Qubits skaliert. Es verwendet den Qiskit-Patterns-Workflow und endet mit der Messung des Erwartungswertes Z0Zi\langle Z_0 Z_i \rangle .

Step 1. Map the problem

Schreibe eine Funktion, die einen QuantumCircuit zurückgibt, der einen nn-Qubit-GHZ-Zustand vorbereitet (im Wesentlichen ein erweiterter Bell-Zustand). Verwende diese Funktion dann, um einen 10-Qubit-GHZ-Zustand vorzubereiten, und sammle die zu messenden Observablen.

def get_qc_for_n_qubit_GHZ_state(n: int) -> QuantumCircuit:

qc = QuantumCircuit(n)
qc.h(0)
for i in range(n-1):
qc.cx(i, i+1)
return qc
n = 10
qc_n_GHZ = get_qc_for_n_qubit_GHZ_state(n)
qc_n_GHZ.draw("mpl")

Quantum circuit diagram

Bilde als nächstes die Operatoren von Interesse ab. Dieses Beispiel verwendet die ZZ-Operatoren zwischen Qubits, um das Verhalten zu untersuchen, wenn sie sich weiter voneinander entfernen. Zunehmend ungenaue (fehlerhafte) Erwartungswerte zwischen weit entfernten Qubits würden das Ausmaß des vorhandenen Rauschens offenbaren.

# ZZII...II, ZIZI...II, ... , ZIII...IZ
operator_strings = [
"Z" + i * "I" + "Z" + "I" * (n-i-2) for i in range(n-1)
]
print(operator_strings)
print(len(operator_strings))

operators = [SparsePauliOp(operator) for operator in operator_strings]
['ZZIIIIIIII', 'ZIZIIIIIII', 'ZIIZIIIIII', 'ZIIIZIIIII', 'ZIIIIZIIII', 'ZIIIIIZIII', 'ZIIIIIIZII', 'ZIIIIIIIZI', 'ZIIIIIIIIZ']
9

Step 2. Optimize the problem for execution on quantum backend

Transformiere den Circuit und die Observablen so, dass sie zur ISA des Backend passen.

# Convert to an ISA circuit and layout-mapped observables.
pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(qc_n_GHZ)
isa_operators_list = [operator.apply_layout(isa_circuit.layout) for operator in operators]

Step 3. Execute on backend

Sende den Job ab und aktiviere, wenn du ihn auf echter Hardware ausführst, Fehlerunterdrückung durch eine Technik zur Reduzierung von Fehlern namens dynamical decoupling. Das Resilience-Level gibt an, wie viel Widerstandsfähigkeit gegenüber Fehlern aufgebaut werden soll. Höhere Level erzeugen genauere Ergebnisse, auf Kosten längerer Verarbeitungszeiten. Weitere Erläuterungen zu den im folgenden Code festgelegten Optionen findest du unter Configure error mitigation for Qiskit Runtime.

# Submit the circuit to Estimator
job = estimator.run([(isa_circuit, isa_operators_list)])
job_id = job.job_id()
/Users/jma/miniconda3/envs/3122/lib/python3.12/site-packages/qiskit_ibm_runtime/fake_provider/local_service.py:187: UserWarning: The resilience_level option has no effect in local testing mode.
warnings.warn("The resilience_level option has no effect in local testing mode.")

Step 4. Post-process results

Um das Verhalten verschränkter Quantenzustände auf echter Hardware besser zu verstehen, analysieren wir die paarweisen Korrelationen zwischen Qubits in der Z-Basis. Konkret betrachten wir die Erwartungswerte ⟨Z₀Zᵢ⟩, die messen, wie stark Qubit 0 mit jedem anderen Qubit i korreliert ist. Insbesondere werden wir folgendes plotten:

ZiZ0/Z1Z0\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle

Welche Werte von ZiZ0/Z1Z0\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle erwartest du im Plot zu sehen?

Optionen:

a) Abnehmend, wenn wir ii erhöhen

b) Konstant bei 1

c) Kleine Abweichungen um 1

d) Abwechselnd 1 und 0 für ungerade und gerade Werte von ii

data = list(range(1, len(operators) + 1))  # Distance between the Z operators
result = job.result()[0]
values = result.data.evs # Expectation value at each Z operator.
values = [
v / values[0] for v in values
] # Normalize the expectation values to evaluate how they decay with distance.

plt.plot(data, values, marker="o", label=f"{n}-qubit GHZ state")
plt.xlabel("Distance between qubits $i$")
plt.ylabel(r"$\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle $")
plt.legend()
plt.show()

Plot output

In diesem Plot stellen wir fest, dass Z0Zi\langle Z_0 Z_i \rangle um den Wert 1 schwankt, obwohl in einer idealen Simulation alle Z0Zi\langle Z_0 Z_i \rangle gleich 1 sein sollten.

Wie du sehen kannst, sind die Ergebnisse der 10-Qubit-Experimente gut, haben aber immer noch einige Fehler. Eine Möglichkeit, die Ergebnisse zu verbessern, besteht darin, den GHZ-Zustand effizienter zu implementieren.

Normalerweise implementiert man den GHZ-Zustand mit einer treppenartigen Sequenz von CNOT-Gates. Du kannst den GHZ-Zustand jedoch effizienter implementieren und dabei die 2-Qubit-Tiefe von n auf n/2 oder weniger reduzieren.

Eine wichtige Metrik, um zu beurteilen, wie genau die Ergebnisse sein werden oder wie wenig Rauschen ein Circuit haben wird, ist die 2-Qubit-Gate-Tiefe. Dies liegt daran, dass die Fehlerraten für 2-Qubit-Gates (~10-mal höher als bei Single-Qubit-Gates) die Fehler des gesamten Circuits dominieren. Verwende den folgenden Code, um die 2-Qubit-Gate-Tiefe eines Circuits zu ermitteln.

qc.depth(lambda x: x.operation.num_qubits == 2)
def better_ghz(n):
"fan out"
s = int(n / 2)
qc = QuantumCircuit(n)
qc.h(s)
for m in range(s, 0, -1):
qc.cx(m, m - 1)
if not (n % 2 == 0 and m == s):
qc.cx(n - m - 1, n - m)
return qc

better_ghz(n).draw("mpl")

Quantum circuit diagram

# Check 2-qubit gate depth before transpilation
qc_better_ghz = better_ghz(n)
qc_better_ghz.depth(lambda x: x.operation.num_qubits == 2)
5

Eine interessante Sache, die hier zu beachten ist: Wir konnten die Quantentiefe des auszuführenden Circuits allein dadurch reduzieren, dass wir clever vorgegangen sind und einen anderen Weg zur Programmierung gefunden haben. Es wird jedoch Situationen und Algorithmen geben, in denen wir uns nicht auf diese cleveren Tricks verlassen können. Hier kommt der Transpiler sehr gelegen – er hilft uns, all diese Aspekte effizient zu optimieren, sodass wir uns nicht allzu sehr darum kümmern müssen.

3. Encoding Information

3.1 Amplitude encoding

Nachdem wir gesehen haben, wie man Quantenschaltkreise aufbaut, ist es interessant zu erkunden, wie wir klassische Informationen in Quantenzustände kodieren können. Eine leistungsstarke Methode ist das Amplitude encoding, bei dem die Amplituden eines Quantenzustands die Komponenten eines klassischen Vektors repräsentieren.

Betrachten wir ein einfaches Beispiel. Angenommen, wir möchten den klassischen Vektor

x=[x0x1x2x3]\vec{x} = \begin{bmatrix} x_0 \\ x_1 \\ x_2 \\ x_3 \end{bmatrix}

in einen Quantenzustand von zwei Qubits kodieren. Das Ziel ist, den Quantenzustand vorzubereiten:

ψ=x000+x101+x210+x311\ket{\psi} = x_0\ket{00} + x_1\ket{01} + x_2\ket{10} + x_3\ket{11}

wobei x0,x1,x2,x3Rx_0, x_1, x_2, x_3 \in \mathbb{R} (oder C\mathbb{C}) und der Vektor normiert ist, sodass:

x02+x12+x22+x32=1|x_0|^2 + |x_1|^2 + |x_2|^2 + |x_3|^2 = 1

Nun betrachten wir das konkrete Beispiel: x=[0.8924,0.3696,0.2391,0.0990]\vec{x} = [0.8924, 0.3696, 0.2391, 0.0990]

Der entsprechende Quantenzustand ist dann:

ψ=0.892400+0.369601+0.239110+0.099011\begin{aligned} \ket{\psi} &= 0.8924\,\ket{00} + 0.3696\,\ket{01} + 0.2391\,\ket{10} + 0.0990\,\ket{11} \end{aligned}

Dieser Zustand kann mit einer Kombination aus Rotationsgattern RyR_y mit den Winkeln π/6\pi/6 und π/4\pi/4 für Qubit 0 bzw. Qubit 1 vorbereitet werden.

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
import numpy as np

qc = QuantumCircuit(2)

qc.ry(np.pi / 6, 0)
qc.ry(np.pi / 4, 1)

simulator = AerSimulator()
qc.save_statevector()
result = simulator.run(qc).result()
statevector = result.get_statevector()

print("Statevector:", statevector)
qc.draw(output="mpl")
Statevector: Statevector([0.8923991 +0.j, 0.23911762+0.j, 0.36964381+0.j,
0.09904576+0.j],
dims=(2, 2))

Quantum circuit diagram

from qiskit.quantum_info import Statevector

# Define our vector
v = np.array([0.8924, 0.3696, 0.2391, 0.0990])
v = v/np.linalg.norm(v)
# Create a statevector from the vector
state = Statevector(v)

# Initialize a quantum circuit with 2 qubits
qc = QuantumCircuit(2)
qc.initialize(state.data, [0, 1])

# Optional: simulate the state
print("Statevector:", state)

# Visualize the circuit
qc.decompose().decompose().decompose().decompose().decompose().draw("mpl")
Statevector: Statevector([0.89242154+0.j, 0.36960892+0.j, 0.23910577+0.j,
0.09900239+0.j],
dims=(2, 2))

Quantum circuit diagram

Damit haben wir gesehen, wie man Informationen mithilfe von Rotationsgattern kodiert.

3.2 Angle encoding and parametrized circuits

Eine besonders interessante Möglichkeit, Informationen in einen Quantencomputer zu kodieren, besteht darin, einen Quantenschaltkreis zu entwerfen, der einige Rotationswinkel θ\vec{\theta} oder Parameter enthält, die angepasst werden können, um eine Familie von Funktionen f(θ)f(\vec{\theta}) darzustellen. Betrachten wir zum Beispiel den folgenden parametrisierten Quantenschaltkreis:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# Define a symbolic parameter
theta = Parameter("θ")

qc = QuantumCircuit(2)
# We applied a parametrized RX gate
qc.rx(theta, 0)
qc.cx(0, 1)
qc.draw("mpl")

Quantum circuit diagram

Mathematisch können wir analysieren, welche Familie von Funktionen wir mit diesem Circuit darstellen können:

CNOT01Rx{0}(θ)00=CNOT01(cos(θ/2)00isin(θ/2)10)=cos(θ/2)00isin(θ/2)11\text{CNOT}_{01} \, R_x^{\{0\}}(\theta) |00\rangle = \text{CNOT}_{01} \left( \cos(\theta/2)\ket{00} - i\sin(\theta/2)\ket{10} \right) = \cos(\theta/2)\ket{00} - i\sin(\theta/2)\ket{11}

Es ist ziemlich klar, dass die Anzahl der Zustände, die wir mit diesem Quantenschaltkreis darstellen können, begrenzt ist, da wir zum Beispiel keine Zustände 10\ket{10} oder 01\ket{01} darstellen können. Die Familie der darstellbaren Zustände beginnt jedoch zu wachsen, wenn wir mehr Rotationen an den richtigen Stellen einführen:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# Define a symbolic parameter
theta1 = Parameter("θ1")
theta2 = Parameter("θ2")

qc = QuantumCircuit(2)
qc.rx(theta1, 0)
qc.rx(theta2, 1)
qc.cx(0, 1)
qc.draw("mpl")

Quantum circuit diagram

In diesem Fall werden die Quantenzustände, die wir darstellen, folgende sein:

\begin{align*} \text{CNOT}_{01} \, R_x^{\{1}}(\theta_2) R_x^{\{0}}(\theta_1) \ket{00} &= \text{CNOT}_{01} \, R_x^{\{1}}(\theta_2)\left( \cos(\theta_1/2)\ket{00} - i\sin(\theta_1/2)\ket{10} \right) \\ &= \text{CNOT}_{01}\left( \cos(\theta_1/2)\cos(\theta_2/2)\ket{00} - i\cos(\theta_1/2)\sin(\theta_2/2)\ket{01} \right. \\ &\quad \left. - i\sin(\theta_1/2)\cos(\theta_2/2)\ket{10} + \sin(\theta_1/2)\sin(\theta_2/2)\ket{11} \right) \\ &= \cos(\theta_1/2)\cos(\theta_2/2)\ket{00} - i\cos(\theta_1/2)\sin(\theta_2/2)\ket{01} \\ &\quad + \sin(\theta_1/2)\sin(\theta_2/2)\ket{10} - i\sin(\theta_1/2)\cos(\theta_2/2)\ket{11} \end{align*}

Wir können sehen, dass dieser Circuit im Vergleich zum vorherigen eine breitere Familie von Quantenzuständen erzeugt. Insbesondere kann er nun Zustände mit von null verschiedenen Amplituden für 01\ket{01} oder 10\ket{10} erzeugen, die mit dem obigen Circuit nicht möglich waren. Dieser Circuit ist jedoch immer noch kein universeller Quantenzustandsgenerator, obwohl er ausdrucksstark genug sein kann, um Circuits mit einer gewissen Flexibilität zur Darstellung bestimmter Funktionen zu entwerfen. Im Allgemeinen gilt: Je mehr unabhängige Parameter (Winkel) wir einführen, desto mehr Ausdruckskraft hat der Circuit, um beliebige Quantenzustände anzunähern.

Ansatzes and Circuit library

Diese Art parametrisierter Quantenschaltkreise kann verwendet werden, um Ansätze zu erstellen – Probe-Quantenzustände, die darauf abzielen, die Lösung eines Problems anzunähern. Diese Ansätze sind ein zentraler Bestandteil von Variationellen Quantenalgorithmen, einer Klasse hybrider quantenklassischer Algorithmen, die einen Quantencomputer zur Auswertung einer Kostenfunktion und einen klassischen Optimierer zu deren Minimierung verwenden. Wir werden in einer späteren Einheit ausführlich auf diese Themen eingehen, aber zunächst zeigen wir, wie man einen einfachen Ansatz mithilfe der Circuit library in Qiskit aufbaut.

from qiskit.circuit.library import efficient_su2

SU2_ansatz = efficient_su2(4, su2_gates=["rx", "y"], entanglement="linear", reps=1)
SU2_ansatz.decompose().draw(output="mpl")

Quantum circuit diagram

Wir haben gesehen, wie man einen einfachen Ansatz mithilfe der Funktion efficient_su2 aus der qiskit.circuit.library aufbaut, der in der Lage ist, durch Anpassen seiner Parameter θ\vec{\theta} eine breite Palette von Quantenzuständen zu erzeugen.

Fazit

In diesem Notebook hast du gelernt, wie man Quantenschaltkreise aufbaut – angefangen von der Konstruktion von Quantengattern über die Definition und Messung von Observablen bis hin zur effizienten Ausführung dieser Circuits auf Simulatoren und echter Quantenhardware. Du hast auch gesehen, wie wichtig ein sorgfältiges Circuit-Design ist, um Fehler bei der Arbeit mit echten Quantengeräten zu minimieren, sowie Strategien zur Skalierung von Circuits auf eine größere Anzahl von Qubits, insbesondere am Beispiel des GHZ-Zustands. Darüber hinaus hast du verschiedene Techniken zur Kodierung klassischer Informationen in Quantenzustände kennengelernt, darunter Amplitude encoding und Angle encoding. Damit bist du bestens gerüstet, um zur nächsten Sitzung überzugehen und mit Quantenalgorithmen zu arbeiten.

Qiskit Code Assistant in VSCode installieren

Klicke auf den Link und folge den Anweisungen.

Bonus: Quantenteleportation

Wenn du den Begriff Quantenteleportation hörst, stellst du dir vielleicht futuristische Science-Fiction-Technologie vor, die ein Objekt an einem Ort zerstäubt und an einem anderen weit entfernten Ort wieder erscheinen lässt. Aber Quantenteleportation ist nichts dergleichen. In Wirklichkeit wird nicht Materie teleportiert, sondern Information.

Quantenteleportation ermöglicht die Übertragung des Quantenzustands eines Qubits von einem Ort zu einem anderen. Obwohl diese Übertragung unmittelbar zu erfolgen scheint, verstößt sie nicht gegen die Gesetze der Physik. Wie ist das möglich? Lass es uns herausfinden!

Quantenteleportation ist ein Protokoll, das es einem Sender (Alice) ermöglicht, den Zustand ψ|\psi\rangle eines Qubits q an einen Empfänger (Bob) zu übertragen, indem zwei Schlüsselressourcen genutzt werden: ein gemeinsames verschränktes Qubit-Paar a und b sowie zwei klassische Kommunikationsbits c0 und c1.

Im Wesentlichen benötigt das Protokoll Folgendes:

  • q: Alices Qubit, anfänglich im Zustand ψ|\psi\rangle, den wir teleportieren möchten.
  • a: Alices Hälfte eines gemeinsamen verschränkten Paares.
  • b: Bobs Hälfte des gemeinsamen verschränkten Paares.
  • c0, c1: Klassische Bits zum Speichern von Alices Messergebnissen.

Und wie funktioniert es? Der Ablauf ist folgender:

  1. Alices Zustand ψ|\psi\rangle auf q vorbereiten. Wir erstellen zur Überprüfung einen bestimmten Zustand wie +|+\rangle.
  2. Verschränkung erzeugen: Ein Bell-Paar zwischen a und b generieren.
  3. Alices Operationen: Alice führt eine „Bell-Messung" an ihren beiden Qubits (q und a) durch und speichert die klassischen Ergebnisse in c0 und c1.
  4. Klassische Kommunikation: Alice sendet ihre zwei klassischen Bits (c0, c1) an Bob.
  5. Bobs Korrekturen: Bob wendet spezifische Quantengatter (X und/oder Z) auf sein Qubit (b) an, abhängig von den Werten von c0 und c1, die er empfangen hat.

Wenn alles korrekt durchgeführt wird, befindet sich Bobs Qubit b am Ende im Zustand ψ|\psi\rangle, dem ursprünglichen Zustand von Alices q!

Für eine ausführlichere Erklärung und Erkundung der Quantenteleportation, einschließlich der mathematischen Erklärung, warum dieses Protokoll funktioniert, kannst du auf die IBM Quantum Learning-Ressourcen zurückgreifen: Quantum Teleportation. Dies ist Teil des Kurses Basics of Quantum Information.


import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram, plot_bloch_multivector

# Define individual quantum registers for each qubit
q = QuantumRegister(1, name='q') # message qubit
a = QuantumRegister(1, name='a') # Alice's entangled qubit
b = QuantumRegister(1, name='b') # Bob's entangled qubit

# Classical register for Alice's measurements
cr_alice = ClassicalRegister(2, name='c_alice')

# Create quantum circuit
teleport_qc = QuantumCircuit(q, a, b, cr_alice, name='Teleportation')

# Step 1: Prepare message state |+⟩ on q
teleport_qc.h(q[0])
teleport_qc.barrier()

# Step 2: Create entanglement between a and b
teleport_qc.h(a[0])
teleport_qc.cx(a[0], b[0])
teleport_qc.barrier()

# Step 3: Alice's Bell measurement
teleport_qc.cx(q[0], a[0])
teleport_qc.h(q[0])
teleport_qc.barrier()

# Step 4: Alice measures q and a
teleport_qc.measure(q[0], cr_alice[0])
teleport_qc.measure(a[0], cr_alice[1])
teleport_qc.barrier()

# Step 5: Bob's conditional measurements
with teleport_qc.if_test((cr_alice[1], 1)):
teleport_qc.x(b[0])
with teleport_qc.if_test((cr_alice[0], 1)):
teleport_qc.z(b[0])

# Draw the circuit
teleport_qc.draw(output='mpl')

Quantum circuit diagram

Nach der Ausführung des Protokolls stellt sich eine wichtige Frage: Wie überprüfen wir, ob die Teleportation funktioniert hat? Wir können den Zustand von Bobs Qubit nach dem Protokoll nicht direkt „sehen". Da wir jedoch Alices Ausgangszustand ψ|\psi\rangle vorbereitet haben (wir haben +|+\rangle gewählt), können wir eine spezielle Art der Simulation verwenden, um zu prüfen, ob Bobs Qubit b in diesem selben Zustand gelandet ist.

Wir werden den AerSimulator mit save_statevector verwenden, um zu prüfen, ob Bobs Qubit b in Alices ursprünglichem Zustand (+|+\rangle) endet. Dieser Simulator berechnet den finalen Quantenzustandsvektor und stellt ihn dann mit plot_bloch_multivector dar, um Bobs Qubit (b) im Vergleich zu Alices Ausgangszustand (q) zu visualisieren.

# Simulate the teleportation circuit
sv_simulator = AerSimulator(method='statevector')
teleport_qc_sv = teleport_qc.copy()
teleport_qc_sv.save_statevector()

# Execute the circuit on the statevector simulator
job_sv = sv_simulator.run(teleport_qc_sv)
result_sv = job_sv.result()

# Get the final statevector
final_statevector = result_sv.get_statevector()
print("Visualizing final qubit states:")
display(plot_bloch_multivector(final_statevector))
print("Note that Alice's qubits have collapsed to |00⟩, |01⟩, |10⟩, or |11⟩, while Bob's qubit is in the original state |+⟩.")
Visualizing final qubit states:

Quantum circuit diagram

Note that Alice's qubits have collapsed to |00⟩, |01⟩, |10⟩, or |11⟩, while Bob's qubit is in the original state |+⟩.

Wie wir aus der Visualisierung ersehen können, sind die ersten beiden Qubits (die zu Alice gehören) auf 0 oder 1 kollabiert. Das dritte Qubit (das zu Bob gehört), dargestellt in der dritten Bloch-Kugel, zeigt hingegen entlang der x-Achse, was anzeigt, dass es sich im Zustand +|+\rangle befindet – wir haben das Quantenteleportationsprotokoll also erfolgreich implementiert!

Zusammenfassung

An diesem Punkt ist es sinnvoll, eine kurze Zusammenfassung dessen zu machen, was wir erreicht haben:

  • Alice hat einen unbekannten Quantenzustand an Bob übertragen.
  • Es wurde kein physikalisches Teilchen übertragen.
  • Der ursprüngliche Zustand auf Alices Qubit wird zerstört, in Übereinstimmung mit dem No-Cloning-Theorem.